package com.witmore.skyline.web.aspect;

import com.fasterxml.jackson.core.type.TypeReference;
import com.witmore.skyline.web.annotation.ApiCache;
import com.witmore.skyline.web.enums.TimePrecision;
import com.witmore.skyline.web.service.cache.RedisCacheService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * API缓存切面
 * 拦截带有@ApiCache注解的方法，自动处理缓存逻辑
 * 
 * Author: 豆子高
 * Date: 2025/10/24
 */
@Slf4j
@Aspect
@Component
public class ApiCacheAspect {

    @Autowired
    private RedisCacheService cacheService;

    @Around("@annotation(apiCache)")
    public Object handleCache(ProceedingJoinPoint joinPoint, ApiCache apiCache) throws Throwable {
        try {
            // 如果不需要缓存，直接执行方法
            if (apiCache.expireMinutes() <= 0) {
                log.debug("方法 {} 不需要缓存，直接执行", joinPoint.getSignature().getName());
                return joinPoint.proceed();
            }

            // 构建缓存key
            String cacheKey = buildCacheKey(joinPoint, apiCache);
            log.debug("构建缓存key: {}", cacheKey);

            // 尝试从缓存获取数据
            Object cachedResult = getCachedResult(cacheKey, joinPoint);
            if (cachedResult != null) {
                log.info("从缓存返回数据, method: {}, cacheKey: {}", joinPoint.getSignature().getName(), cacheKey);
                return cachedResult;
            }

            // 缓存未命中，执行原方法
            log.debug("缓存未命中，执行原方法: {}", joinPoint.getSignature().getName());
            Object result = joinPoint.proceed();

            // 将结果存入缓存
            if (result != null) {
                cacheService.setCache(cacheKey, result, apiCache.expireMinutes());
                log.info("方法执行结果已缓存, method: {}, cacheKey: {}, expireMinutes: {}", 
                        joinPoint.getSignature().getName(), cacheKey, apiCache.expireMinutes());
            }

            return result;
            
        } catch (Exception e) {
            // 全局异常捕获：切面处理失败时降级到直接调用原方法
            log.error("缓存切面处理失败，降级到直接调用原方法, method: {}, error: {}", 
                    joinPoint.getSignature().getName(), e.getMessage(), e);
            
            // 降级策略：直接执行原方法，确保业务不受影响
            return joinPoint.proceed();
        }
    }

    /**
     * 构建缓存key
     */
    private String buildCacheKey(ProceedingJoinPoint joinPoint, ApiCache apiCache) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        
        // 使用自定义前缀或默认前缀
        String prefix;
        if (!apiCache.keyPrefix().isEmpty()) {
            prefix = apiCache.keyPrefix();
        } else {
            prefix = method.getDeclaringClass().getSimpleName() + ":" + method.getName();
        }

        // 如果没有指定keyParams，只使用前缀
        if (apiCache.keyParams().length == 0) {
            return prefix;
        }

        // 获取时间参数集合，用于特殊处理
        Set<String> timeParamSet = new HashSet<>(Arrays.asList(apiCache.timeParams()));
        
        // 根据keyParams构建缓存key
        String[] paramNames = signature.getParameterNames();
        Object[] paramValues = joinPoint.getArgs();
        
        StringBuilder keyBuilder = new StringBuilder(prefix);
        
        for (String keyParam : apiCache.keyParams()) {
            // 查找参数名对应的值
            for (int i = 0; i < paramNames.length; i++) {
                if (keyParam.equals(paramNames[i])) {
                    Object paramValue = paramValues[i];
                    
                    // 如果是时间参数，进行时间精度处理
                    if (timeParamSet.contains(keyParam) && paramValue instanceof String) {
                        paramValue = processTimeParam((String) paramValue, apiCache.timePrecision(), apiCache.expireMinutes());
                    }
                    
                    keyBuilder.append(":").append(paramValue);
                    break;
                }
            }
        }
        
        return keyBuilder.toString();
    }

    /**
     * 处理时间参数，根据时间精度进行截断或窗口对齐
     */
    private String processTimeParam(String timeStr, TimePrecision precision, long cacheMinutes) {
        try {
            // 支持多种时间格式
            LocalDateTime dateTime = parseDateTime(timeStr);
            if (dateTime == null) {
                return timeStr; // 解析失败，返回原值
            }
            
            // 根据精度进行处理
            switch (precision) {
                case MINUTE:
                    dateTime = dateTime.withSecond(0).withNano(0);
                    break;
                case HOUR:
                    dateTime = dateTime.withMinute(0).withSecond(0).withNano(0);
                    break;
                case DAY:
                    dateTime = dateTime.withHour(0).withMinute(0).withSecond(0).withNano(0);
                    break;
                case CACHE_WINDOW:
                    // 缓存窗口对齐：将时间对齐到缓存窗口边界
                    dateTime = alignToCacheWindow(dateTime, cacheMinutes);
                    break;
                case SECOND:
                default:
                    // 保持原精度
                    break;
            }
            
            // 返回处理后的时间字符串
            return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            
        } catch (Exception e) {
            log.warn("时间参数处理失败，使用原值: {}, error: {}", timeStr, e.getMessage());
            return timeStr;
        }
    }

    /**
     * 将时间对齐到缓存窗口边界
     * @param dateTime 原始时间
     * @param cacheMinutes 缓存时间（分钟）
     * @return 对齐后的时间
     */
    private LocalDateTime alignToCacheWindow(LocalDateTime dateTime, long cacheMinutes) {
        if (cacheMinutes <= 0) {
            log.warn("缓存时间配置无效: {} 分钟，使用原始时间", cacheMinutes);
            return dateTime;
        }
        
        // 获取当前分钟数
        int currentMinute = dateTime.getMinute();
        
        // 计算对齐到的分钟数：(currentMinute / cacheMinutes) * cacheMinutes
        int alignedMinute = (int) ((currentMinute / cacheMinutes) * cacheMinutes);
        
        // 返回对齐后的时间（秒和纳秒清零）
        LocalDateTime alignedTime = dateTime.withMinute(alignedMinute).withSecond(0).withNano(0);
        
        log.debug("时间窗口对齐: {} -> {}, 缓存窗口: {}分钟", 
                dateTime.format(DateTimeFormatter.ofPattern("HH:mm:ss")), 
                alignedTime.format(DateTimeFormatter.ofPattern("HH:mm:ss")), 
                cacheMinutes);
        
        return alignedTime;
    }

    /**
     * 解析时间字符串，支持多种格式
     */
    private LocalDateTime parseDateTime(String timeStr) {
        if (timeStr == null || timeStr.trim().isEmpty()) {
            return null;
        }
        
        // 常见的时间格式
        String[] patterns = {
            "yyyy-MM-dd HH:mm:ss",
            "yyyy-MM-dd HH:mm",
            "yyyy-MM-dd",
            "yyyy/MM/dd HH:mm:ss",
            "yyyy/MM/dd HH:mm",
            "yyyy/MM/dd"
        };
        
        for (String pattern : patterns) {
            try {
                return LocalDateTime.parse(timeStr, DateTimeFormatter.ofPattern(pattern));
            } catch (Exception ignored) {
                // 继续尝试下一种格式
            }
        }
        
        return null;
    }

    /**
     * 从缓存获取结果
     */
    private Object getCachedResult(String cacheKey, ProceedingJoinPoint joinPoint) {
        try {
            // 获取方法返回类型
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Type returnType = method.getGenericReturnType();
            
            // 创建TypeReference
            TypeReference<?> typeReference = new TypeReference<Object>() {
                @Override
                public Type getType() {
                    return returnType;
                }
            };
            
            return cacheService.getCache(cacheKey, typeReference);
        } catch (Exception e) {
            log.error("获取缓存数据失败, cacheKey: {}, error: {}", cacheKey, e.getMessage(), e);
            return null;
        }
    }
}
